﻿// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Framework;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Validation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;

using static DocumentFormat.OpenXml.Tests.TestAssets;

namespace DocumentFormat.OpenXml.Tests
{
    public class DocumentOpenTests
    {
        [Fact]
        public void ThrowIfFileCannotBeFound()
        {
            const string NonExistantFile = "Unknown";

            Assert.False(File.Exists(NonExistantFile));

            Assert.Throws<FileNotFoundException>(() => SpreadsheetDocument.Open(NonExistantFile, true));
            Assert.Throws<FileNotFoundException>(() => SpreadsheetDocument.Open(NonExistantFile, false));
            Assert.Throws<FileNotFoundException>(() => SpreadsheetDocument.Open(NonExistantFile, true, new OpenSettings()));
            Assert.Throws<FileNotFoundException>(() => SpreadsheetDocument.Open(NonExistantFile, false, new OpenSettings()));

            Assert.Throws<FileNotFoundException>(() => WordprocessingDocument.Open(NonExistantFile, true));
            Assert.Throws<FileNotFoundException>(() => WordprocessingDocument.Open(NonExistantFile, false));
            Assert.Throws<FileNotFoundException>(() => WordprocessingDocument.Open(NonExistantFile, true, new OpenSettings()));
            Assert.Throws<FileNotFoundException>(() => WordprocessingDocument.Open(NonExistantFile, false, new OpenSettings()));

            Assert.Throws<FileNotFoundException>(() => PresentationDocument.Open(NonExistantFile, true));
            Assert.Throws<FileNotFoundException>(() => PresentationDocument.Open(NonExistantFile, false));
            Assert.Throws<FileNotFoundException>(() => PresentationDocument.Open(NonExistantFile, true, new OpenSettings()));
            Assert.Throws<FileNotFoundException>(() => PresentationDocument.Open(NonExistantFile, false, new OpenSettings()));
        }

        [Theory]
        [InlineData(FileAccess.Read)]
        [InlineData(FileAccess.ReadWrite)]
        public void RewriteMalformedUriLong(FileAccess access)
        {
            // Arrange
            const string ExpectedMalformedUri = "mailto:test@test.com;%20test2@test.com;%252test3@test.com;%20test3@test.com;%20test4@test.com;%20test5@test.com?subject=Unsubscribe%20Request&body=Please%20unsubscribe%20me%20from%20all%20future%20communications";
            const string Id = "rId1";

            using var file = OpenFile(TestFiles.Malformed_uri_long_xlsx, access);
            using var stream = file.Open();
            using var doc = SpreadsheetDocument.Open(stream, isEditable: file.IsEditable);

            // Act
            var worksheetPart = doc.WorkbookPart.WorksheetParts.Single();
            var hyperlinkRelationship = worksheetPart.HyperlinkRelationships.Single();
            var worksheet = Assert.IsType<Worksheet>(worksheetPart.RootElement);
            var hyperlink = Assert.Single(worksheet.Descendants<Hyperlink>());

            // Assert
            Assert.Equal(Id, hyperlinkRelationship.Id);
            Assert.Equal(Id, hyperlink.Id);
            Assert.Equal(ExpectedMalformedUri, hyperlinkRelationship.Uri.ToString());
        }

        [Theory]
        [InlineData(FileAccess.Read)]
        [InlineData(FileAccess.ReadWrite)]
        public void RewriteMalformedUri(FileAccess access)
        {
            // Arrange
            const string Id = "rId1";

            using var file = OpenFile(TestFiles.Malformed_uri_xlsx, access);
            using var stream = file.Open();
            using var doc = SpreadsheetDocument.Open(stream, isEditable: file.IsEditable);

            // Act
            var worksheetPart = doc.WorkbookPart.WorksheetParts.Single();
            var hyperlinkRelationship = worksheetPart.HyperlinkRelationships.Single();
            var worksheet = Assert.IsType<Worksheet>(worksheetPart.RootElement);
            var hyperlink = Assert.Single(worksheet.Descendants<Hyperlink>());

            // Assert
            Assert.Equal(Id, hyperlink.Id);
            Assert.Equal(Id, hyperlinkRelationship.Id);
            Assert.Equal("mailto:one@", hyperlinkRelationship.Uri.ToString());
        }

        [Fact]
        public void NonSeekableRewriteMalformedUri()
        {
            // Arrange
            using var stream = GetStream(TestFiles.Malformed_uri_xlsx);

            var exception = Assert.Throws<OpenXmlPackageException>(() =>
            {
                using var doc = SpreadsheetDocument.Open(new NonSeekableStream(stream), isEditable: false);

                // Act/Assert
                var worksheetPart = doc.WorkbookPart.WorksheetParts.Single();
                var link = worksheetPart.HyperlinkRelationships.Single();
            });

#if NETFRAMEWORK
            Assert.IsType<ArgumentException>(exception.InnerException);
#else
            Assert.IsType<UriFormatException>(exception.InnerException);
#endif
        }

        [Fact]
        public void NonSeekableRewriteMalformedUriCompatMode()
        {
            // Arrange
            using var stream = GetStream(TestFiles.Malformed_uri_xlsx);

            // Act/Assert
            var exception = Assert.Throws<OpenXmlPackageException>(() => SpreadsheetDocument.Open(new NonSeekableStream(stream), isEditable: false, new OpenSettings { CompatibilityLevel = CompatibilityLevel.Version_2_20 }));

#if NETFRAMEWORK
            Assert.IsType<ArgumentException>(exception.InnerException);
#else
            Assert.IsType<UriFormatException>(exception.InnerException);
#endif
        }

        private sealed class NonSeekableStream : DelegatingStream
        {
            public NonSeekableStream(Stream innerStream)
                : base(innerStream)
            {
            }

            public override bool CanSeek => false;

            public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();

            public override long Position
            {
                get => throw new NotSupportedException();
                set => throw new NotSupportedException();
            }

            public override long Length => throw new NotSupportedException();

            public override void SetLength(long value) => throw new NotSupportedException();
        }
    }
}
